#include <ctype.h>
#include <libgen.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <linux/hdreg.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <net/if.h>
#include <unistd.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <mntent.h>
#include <sys/stat.h>
#include <dirent.h>
#include <syslog.h>
#include <linux/ethtool.h>
#include <linux/sockios.h>
#include <glib.h>

#include <blkid/blkid.h>

#include <cdio/cdio.h>
#include <syslog.h>
#include <sys/types.h>
#include <unistd.h>

#include "misc.h"
#include "encrypt.h"
#include "hwid.h"

#define DEFAULT_DISK_DEVICE "/dev/sda"
#define PROC_MOUNTS "/proc/mounts"
#define PROC_DISKSTATS "/proc/diskstats"

#define DMI_UUID_FILE "/sys/devices/virtual/dmi/id/product_uuid"

#define CMDLINE_FILE "/proc/cmdline"
#define COUINFO_FILE "/proc/cpuinfo"
#define BONDING_DIR_PATH "/proc/net/bonding" 
#define CID_FILE "/proc/bootdevice/cid"

#define BUF_SIZE 1024
#define ROOTFS_DEV "ROOTFS_DEVICE"
#define SERIAL_NO_LEN 20
#define MODEL_LEN 40

#define HW_CODE_LENGTH 20

#define KYLIN_HWID "/etc/.kyhwid"

#undef ON_PLATFORM_X86
//#if defined __x86_64 || defined __x86_64__ || defined __amd64 || defined __amd64__ || defined __i386 || defined __i386__ || defined __i686 || defined __i686__
//#define ON_PLATFORM_X86
//#endif


/*
 * harddisk_id, get harddisk information from disk name
 */
static char* harddisk_id(const char* disk_name);

/*
 * hardware_id_match, check if hwid matches encrypted_hwid
 * return 1 if matches,  or 0 if not
 */
static int hardware_id_match(const char* hwid, const char* encrypted_hwid);

/*
 * root_device, get root device name
 */
static char* root_device();

/*
 * _is_block_device, check file with dev_name if a block device
 * return 1 if yes, or 0 if not
 */
static int _is_block_device(const char* dev_name);

struct block_device
{
	char* name;
	char* id;
};
static uid_t uid = 0;

typedef struct {
	unsigned char byte[6];
} mac_t;

/*
static void block_device_free(gpointer data, gpointer userdata)
{
	struct block_device* blk_dev;
	blk_dev = (struct block_device*)data;

	if(!blk_dev)
		return;

	if(blk_dev->name)
		free(blk_dev->name);
	if(blk_dev->id)
		free(blk_dev->id);

	free(blk_dev);
}
*/

/*
static void block_device_list_free(GList* list)
{
	g_list_foreach(list, block_device_free, NULL);
	g_list_free(list);
}
*/

/*
static struct block_device* block_device_create(const char* device_name, const char* device_id)
{
	struct block_device* blk_dev = NULL;
	char* name = NULL;
	char* id = NULL;

	blk_dev = (struct block_device*)malloc(sizeof(struct block_device));

	if(!blk_dev)
		return NULL;

	name = strdup(device_name);
	if(!name)
	{
		free(blk_dev);
		return NULL;
	}

	id = strdup(device_id);
	if(!id)
	{
		free(blk_dev);
		free(name);
		return NULL;
	}

	blk_dev->name = name;
	blk_dev->id = id;

	return blk_dev;
}
*/

/*
static GList* block_device_list_create()
{
	FILE* fp;
	char line[BUF_SIZE];
	char stat_name[BUF_SIZE];
	char dev_name[BUF_SIZE];
	int i = -1;
	int major, minor;

	GList* list = NULL;
	struct block_device* blk_dev = NULL;
	char* id = NULL;

	unsigned int ios_pgr, tot_ticks, rq_ticks, wr_ticks;
	unsigned long rd_ios, rd_merges_or_rd_sec, rd_ticks_or_wr_sec, wr_ios;
	unsigned long wr_merges, rd_sec_or_wr_ios, wr_sec;

	if((fp = fopen(PROC_DISKSTATS, "r")) == NULL)
		return NULL;

	memset(line, 0, BUF_SIZE);
	memset(stat_name, 0, BUF_SIZE);

	while(fgets(line, sizeof(line), fp) != NULL)
	{
		i = sscanf(line, "%u %u %s %lu %lu %lu %lu %lu %lu %lu %u %u %u %u",
			&major, &minor, stat_name, &rd_ios, &rd_merges_or_rd_sec,
			&rd_sec_or_wr_ios, &rd_ticks_or_wr_sec, &wr_ios, &wr_merges,
			&wr_sec, &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks);

		if(i < 3)
			continue;

		memset(dev_name, 0, BUF_SIZE);
		if(strncmp(stat_name, "/dev/", 5) == 0)
			memcpy(dev_name, stat_name, strlen(stat_name));
		else
			sprintf(dev_name, "/dev/%s", stat_name);

		id = harddisk_id(dev_name);

		if(id)
		{
			blk_dev = block_device_create(dev_name, id);

			if(blk_dev)
			{
				list = g_list_append(list, (gpointer)blk_dev);
			}

			free(id);
			id = NULL;
		}
	}

	return list;
}
*/

struct network_interface
{
	char* name;
	char* mac;
	char* permanent_mac;
};

/*
 * interface_compare, compare interface's mac
 * return 0 if two mac is the same, number below if a > b, number above 0 if a < b
 */
static gint interface_compare(gconstpointer a, gconstpointer b)
{
	struct network_interface *if_a, *if_b;
	if_a = (struct network_interface *)a;
	if_b = (struct network_interface *)b;

	if(if_a && if_b && if_a->permanent_mac && if_b->permanent_mac)
		return - strcmp(if_a->permanent_mac, if_b->permanent_mac);

	return 0;
}

/*
 * network_interface_free,  free network_interface struct
 */
static void network_interface_free(gpointer data, gpointer user_data)
{
	struct network_interface* iface = NULL;
	iface = (struct network_interface*)data;

	if(iface)
	{
		if(iface->name)
			free(iface->name);
		if(iface->mac)
			free(iface->mac);
		if(iface->permanent_mac)
			free(iface->permanent_mac);

		free(iface);
	}
}

/*
static void print_interface(gpointer data, gpointer user_data)
{
	struct network_interface* iface = NULL;
	iface = (struct network_interface*)data;

	if(iface)
		printf("Name: %s\nMac:  %s\n\n", iface->name, iface->mac);
}
*/

/*
 * create a object of struct network_interface
 */
static struct network_interface* 
network_interface_create(char* iface_name, char* mac_address, char* permanent_mac_address)
{
	struct network_interface *iface;
	char* name;
	char* mac;
	char* permanent_mac;
	
	iface = (struct network_interface*)malloc(sizeof(struct network_interface));
	if(!iface)
		return 0;

	name = strdup(iface_name);
	if(!name)
	{
		free(iface);
		return NULL;
	}

	mac = strdup(mac_address);
	if(!mac)
	{
		free(iface);
		free(name);
		return NULL;
	}

	permanent_mac = strdup(permanent_mac_address);
	if(!permanent_mac)
	{
		free(iface);
		free(name);
		free(mac);
		return NULL;
	}

	iface->name = name;
	iface->mac = mac;
	iface->permanent_mac = permanent_mac;

	return iface;
}

/*
 * Whether it is mounted on the pci bus
 */
static bool is_mounted_pci_bus(const char* ifa_name)
{
	int fd = -1;
	bool bRet = false;
	struct ifreq ifr;
	struct ethtool_drvinfo drvinfo;
	DIR *dir = opendir("/sys/bus/pci/devices");
	struct dirent *pdirent = NULL;
	GList* list = NULL;
	GList *l = NULL;
	char *pci_device = NULL;

	fd = socket(AF_INET, SOCK_DGRAM, 0);
	if(fd < 0) {
		bRet = false;
		goto out;
	}

	memset(&ifr, 0, sizeof(struct ifreq));
  	strncpy(ifr.ifr_name, ifa_name, strlen(ifa_name));
	
	drvinfo.cmd = ETHTOOL_GDRVINFO;
	ifr.ifr_data = (caddr_t)&drvinfo;
	if(ioctl(fd, SIOCETHTOOL, &ifr) == -1) {
		syslog(LOG_INFO,"[%d] <%s/%d> error.",uid,__func__,__LINE__);
	}
	close(fd);
	// syslog(LOG_INFO,"[%d] <%s/%d>: bus info: %s.",uid,__func__,__LINE__,drvinfo.bus_info);

	while((pdirent = readdir(dir)) != NULL) { //把. ..过滤掉
		if(strcmp(pdirent->d_name, ".") == 0 ||  strcmp(pdirent->d_name, "..") == 0)
			continue;
		list = g_list_append(list, (gpointer)pdirent->d_name);
	}
	for(l = list; l != NULL; l = l->next) {
		if(!l->data)
			continue;

		pci_device = (char*)l->data;
		if (strlen(drvinfo.bus_info) > 0 && strstr(pci_device, drvinfo.bus_info) != NULL) {
			syslog(LOG_INFO,"[%d] <%s>:pci device: %s=%s.",uid,__func__,pci_device,drvinfo.bus_info);
			bRet = true;
			goto out;
		}
	}
	

out:
	if (NULL != dir)
		closedir(dir);
	return bRet;
}

/*
 * network_interface_get_mac, get the mac string of a interface from interface name
 */
static char* network_interface_get_mac(const char* interface_name)
{
	int fd = -1;
	struct ifreq ifr;
	unsigned char *mac;
	char* str;
	int i;

	char buf[BUF_SIZE];
	memset(buf, 0, BUF_SIZE);
	fd = socket(AF_INET, SOCK_DGRAM, 0);
	if(fd < 0)
		return NULL;

	ifr.ifr_addr.sa_family = AF_INET;
	strncpy(ifr.ifr_name, interface_name, IFNAMSIZ - 1);

	if(ioctl(fd, SIOCGIFHWADDR, &ifr))
	{
		close(fd);
		return NULL;
	}

	close(fd);

	mac = (unsigned char *)ifr.ifr_hwaddr.sa_data;

	sprintf(buf, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" , mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

	str = strdup(buf);

	for(i = 0; i < strlen(str); i++)
	{
		str[i] = tolower(str[i]);
	}

	return str;

}

/*
 * get the permanent mac string of a interface from interface name
 */
static char* 
network_interface_get_permanent_mac(const char* interface_name)
{
	int                       i;
	struct ifreq              req;
	struct ethtool_perm_addr *epa;
	mac_t                    *newmac;
	char					 *str;
	struct ethtool_drvinfo   drvinfo;
	int 					 fd = -1;
	char 					 buf[BUF_SIZE];
	memset(buf, 0, BUF_SIZE);

	strncpy(req.ifr_name, interface_name, strlen(interface_name));

	fd = socket(AF_INET, SOCK_DGRAM, 0);
	if(fd < 0) {
		return NULL;
	}

	if (ioctl(fd, SIOCGIFHWADDR, &req) < 0) {
		perror ("[ERROR] Set device name");
		close(fd);
		return NULL;
	}

	newmac = (mac_t *) calloc (1, sizeof(mac_t));

	epa = (struct ethtool_perm_addr*) malloc(sizeof(struct ethtool_perm_addr) + IFHWADDRLEN);
	epa->cmd = ETHTOOL_GPERMADDR;
	epa->size = IFHWADDRLEN;

	req.ifr_data = (caddr_t)epa;

	if (ioctl(fd, SIOCETHTOOL, &req) < 0) {
		perror ("[ERROR] Could not read permanent MAC");
	} else {
		for (i=0; i<6; i++) {
			newmac->byte[i] = epa->data[i];
		}
	}

	free(epa);
	close(fd);

	sprintf(buf, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" , 
			newmac->byte[0], newmac->byte[1], newmac->byte[2], newmac->byte[3], newmac->byte[4], newmac->byte[5]);

	str = strdup(buf);

	for(i = 0; i < strlen(str); i++) {
		str[i] = tolower(str[i]);
	}

	free(newmac);
	return str;
}


/*
 * remove_new_line_char, remove '\n' in string and replace it with '\0'.
 */
static void remove_new_line_char(char* buf)
{
	char* p = buf;

	if(!buf)
		return;

	while(*p != '\0')
	{
		if(*p == '\n')
		{
			*p = '\0';
			break;
		}
		p++;
	}
}

/*
 * string_to_lower, convert character in string to lower form.
 */
static void string_to_lower(char* str)
{
	int i;
	int len;

	if(!str)
		return;

	len = strlen(str);
	if(len == 0)
		return;

	for(i = 0; i < len; i++)
	{
		str[i] = tolower(str[i]);
	}
}

/*
 * bonding_file_get_network_interface_list, get network interface information from a bonding file.
 */
static GList* bonding_file_get_network_interface_list(const char* path)
{
	FILE *fp = NULL;
	char buf[BUF_SIZE] = {0};
	GList *list = NULL;
	char iface_name[128] = {0};
	char iface_mac[128] = {0};
	struct network_interface *iface = NULL;


	fp = fopen(path, "r");

	if(!fp)
	{
		return NULL;
	}

	while(fgets(buf, BUF_SIZE, fp))
	{
		memset(iface_name, 0, sizeof(iface_name));
		if(sscanf(buf, "Slave Interface: %s", iface_name) == 1)
		{
			memset(iface_mac, 0, sizeof(iface_mac));

			remove_new_line_char(iface_name);
			while(fgets(buf, BUF_SIZE, fp))
			{
				if(sscanf(buf, "Permanent HW addr: %s", iface_mac) == 1)
				{
					remove_new_line_char(iface_mac);
					string_to_lower(iface_mac);
					break;
				}
			}

			if((strlen(iface_name) > 0) && (strlen(iface_mac) > 0))
			{
				iface = network_interface_create(iface_name, iface_mac, iface_mac);
				if(iface)
				{
					list = g_list_append(list, (gpointer)iface);
				}
			}
		}
	}

	fclose(fp);

	return list;
}

/*
 * bonding_get_network_interface_list, get network interface information from bonding.
 */
static GList* bonding_get_network_interface_list()
{
	DIR *dp = NULL;
	GList* list = NULL;
	GList* l;
	struct dirent *dir = NULL;
	char path[BUF_SIZE] = {0};
	struct stat sb;

	dp = opendir(BONDING_DIR_PATH);
	if(!dp)
		return NULL;

	while((dir = readdir(dp)) != NULL)
	{
		if((strcmp(dir->d_name, ".") == 0) || (strcmp(dir->d_name, "..") == 0))
			continue;

		sprintf(path, "%s/%s", BONDING_DIR_PATH, dir->d_name);

		if(stat(path, &sb) != 0)
			continue;

		if(!S_ISREG(sb.st_mode))
			continue;

		l = bonding_file_get_network_interface_list(path);
		if(l)
		{
			list = g_list_concat(list, l);
		}
	}

	closedir(dp);

	return list;
}

/*
 * create a list for network interface devices
 */
static GList* network_interface_list_create()
{
	GList* list = NULL;
	GList* bonding_list = NULL;
	struct ifaddrs *addrs, *padd;
	char* mac = NULL,*permanent_mac = NULL;
	struct network_interface* iface;	

	if(getifaddrs(&addrs) == -1)
		return NULL;

	padd = addrs;

	while(padd)
	{
		// syslog(LOG_INFO,"[%d] <%s/%d>: found ifa device: %s.",uid,__func__,__LINE__,padd->ifa_name);
		if(padd->ifa_addr && (
			(padd->ifa_addr->sa_family == AF_INET) ||
			(padd->ifa_addr->sa_family == AF_INET6) ||
			(padd->ifa_addr->sa_family == AF_PACKET)
			))
		{
			if(padd->ifa_name && (
			        (strncmp(padd->ifa_name, "eth", 3) == 0) ||
			        (strncmp(padd->ifa_name, "en",  2) == 0) ||
			        (strncmp(padd->ifa_name, "em",  2) == 0)
				))
			{
				// Whether it is mounted on the pci bus
				if(!is_mounted_pci_bus(padd->ifa_name))
					break;

				mac = network_interface_get_mac(padd->ifa_name);
				if(mac == NULL) {
					break;
				}

				permanent_mac = network_interface_get_permanent_mac(padd->ifa_name);
				if(permanent_mac == NULL) {
					break;
				}

				iface = network_interface_create(padd->ifa_name, mac, permanent_mac);

				if (mac) {
					free(mac);
					mac = NULL;
				}

				if (permanent_mac) {
					free(permanent_mac);
					permanent_mac = NULL;
				}

				if(iface)
					list = g_list_append(list, (gpointer)iface);
			}
		}
		padd = padd->ifa_next;
	}

	if(addrs)
	{
		freeifaddrs(addrs);
	}

	bonding_list = bonding_get_network_interface_list();

	if(bonding_list)
	{
		list = g_list_concat(list, bonding_list);
	}

	return list;
}


/*
 * network_interface_list_free, free network interface list
 */
static void network_interface_list_free(GList* list)
{
	g_list_foreach(list, network_interface_free, NULL);
	g_list_free(list);
}

/*
 * network_interface_get_max_mac, reorder network interface list and get interface for the max mac
 */
static char* network_interface_get_max_mac()
{
	GList* list;
	GList* first;
	char* permanent_mac = NULL;
	struct network_interface* iface;

	list = network_interface_list_create();
	list = g_list_sort(list, interface_compare);

	first = g_list_first(list);

	if(first) {
		iface = (struct network_interface*)(first->data);

		if(iface && iface->permanent_mac)
			permanent_mac = strdup(iface->permanent_mac);
	}

	network_interface_list_free(list);

	return permanent_mac;
}

/*
 * find a network interface which its mac matches
 * return GList if the interface matches, or NULL if no one matches
 */
static char* 
network_interface_list_id_match(GList* list, const char* encrypted_hwid)
{
	GList* l;
	struct network_interface* iface;

	for(l = list; l != NULL; l = l->next) {
		if(!l->data)
			continue;

		iface = (struct network_interface*)l->data;
		if(!iface->permanent_mac)
			continue;

		if(hardware_id_match(iface->permanent_mac, encrypted_hwid)) {
			//try to match encrypted_hwid with permanent_mac
			// syslog(LOG_INFO,"[%d] <%s> match encrypted_hwid with permanent_mac: %s.",uid,__func__,iface->permanent_mac);
			return iface->permanent_mac;
		} else if(hardware_id_match(iface->mac, encrypted_hwid)) {
			//not match encrypted_hwid with permanent_mac
			// syslog(LOG_INFO,"[%d] <%s> match encrypted_hwid with mac: %s.",uid,__func__,iface->mac);
			return iface->mac;
		}
	}

	return NULL;
}


/*
 * partition_get_fs_uuid, get uuid of a partition
 */
static char* partition_get_fs_uuid(const char* partition_name)
{
	blkid_probe pr;
	const char* fs_uuid = NULL;

	if(!partition_name)
		return NULL;

	pr = blkid_new_probe_from_filename(partition_name);
	if(!pr)
		return NULL;

	blkid_do_probe(pr);

	if(blkid_probe_has_value(pr, "UUID"))
		blkid_probe_lookup_value(pr, "UUID", &fs_uuid, NULL);

	blkid_free_probe(pr);

	if(partition_name) {
		free(partition_name);
		partition_name = NULL;
	}

	if(fs_uuid)
		return strdup(fs_uuid);

	return NULL;
}

/*
 * root_device_fs_uuid, get uuid of root device partition
 */
static char* root_device_fs_uuid()
{
	char* rootfs_dev = NULL;
	char* cRet = NULL;

	rootfs_dev = root_device();
	if(!rootfs_dev)
		return NULL;

	cRet = partition_get_fs_uuid(rootfs_dev);
	if (rootfs_dev) {
		free(rootfs_dev);
		rootfs_dev = NULL;
	}
	return cRet;
}

/*
 * hardware_id_from_fs_uuid, get hardware information from file system uuid
 */
static char* hardware_id_from_fs_uuid(const char* encrypted_hwid)
{
	char* fs_uuid = NULL;

	fs_uuid = root_device_fs_uuid();
	if(!fs_uuid)
		return NULL;

	if(hardware_id_match(fs_uuid, encrypted_hwid))
		return fs_uuid;
	else
		free(fs_uuid);

	return NULL;
}


#ifdef ON_PLATFORM_X86

/*
 * system_uuid_skip_list,
 * if a system-uuid is in this list,
 * this uuid will not be used to generate hardware information.
 */

static const char* system_uuid_skip_list[] = {
	"12345678-1234-5678-90ab-cddeefaabbcc",
	"03000200-0400-0500-0006-000700080009",
	"fefefefe-fefe-fefe-fefe-fefefefefefe",
};

/*
 * system_uuid_should_skip, tell if a system-uuid should be skipped.
 * return 1 if it should be skipped, or return 0.
 */

static int system_uuid_should_skip(const char* uuid)
{
	int i = 0;

	for (i = 0; i < sizeof(system_uuid_skip_list) / sizeof(char*); i++)
	{
		if (strcasecmp(uuid, system_uuid_skip_list[i]) == 0)
		{
			return 1;
		}
	}

	return 0;
}

#endif

/*
 * system_uuid_from_sysfs, get system uuid from sys file system
 */
#ifdef ON_PLATFORM_X86
static char* system_uuid_from_sysfs(const char* file_name)
{
	FILE * fp = NULL;
	char buf[BUF_SIZE];
	int i;

	fp = fopen(file_name, "r");
	if(!fp)
		return NULL;

	memset(buf, 0, BUF_SIZE);

	if(!fgets(buf, BUF_SIZE, fp))
	{
		fclose(fp);
		return NULL;
	}

	for(i = 0; i < BUF_SIZE; i++)
	{
		if((buf[i] == '\n') || (buf[i] == '\r'))
			buf[i] = '\0';
	}
	buf[BUF_SIZE - 1] = '\0';

	fclose(fp);

	if (system_uuid_should_skip(buf))
		return NULL;

	return strdup(buf);
}
#endif

/*
 * system_uuid_from_dmidecode, get system uuid from dmidecode
 */
#ifdef ON_PLATFORM_X86
static char* system_uuid_from_dmidecode()
{
	FILE* fp;
	char buf[BUF_SIZE];
	int i;

	if(access("/usr/sbin/dmidecode", F_OK | R_OK | X_OK) != 0)
		return NULL;

	fp = popen("/usr/sbin/dmidecode -s system-uuid | grep '[0-9a-fA-F]\\{8\\}-[0-9a-fA-F]\\{4\\}-[0-9a-fA-F]\\{4\\}-[0-9a-fA-F]\\{4\\}-[0-9a-fA-F]\\{12\\}'", "r");
	if(!fp)
		return NULL;

	memset(buf, 0, BUF_SIZE);
	if(!fgets(buf, BUF_SIZE, fp))
	{
		pclose(fp);
		return NULL;
	}

	for(i = 0; i < BUF_SIZE; i++)
	{
		if((buf[i] == '\n') || (buf[i] == '\r'))
			buf[i] = '\0';
	}
	buf[BUF_SIZE - 1] = '\0';

	pclose(fp);

	if (system_uuid_should_skip(buf))
		return NULL;

	return strdup(buf);
}
#endif

/*
 * system_uuid, get system uuid
 */
#ifdef ON_PLATFORM_X86
static char* system_uuid()
{
	char* sys_uuid = NULL;

	sys_uuid = system_uuid_from_sysfs(DMI_UUID_FILE);

	if(sys_uuid)
		return sys_uuid;

	sys_uuid = system_uuid_from_dmidecode();
	
	return sys_uuid;
}
#endif



/*
 * hardware_id_from_system_uuid, get hardware information from system uuid
 */
static char* hardware_id_from_system_uuid(const char* encrypted_hwid)
{
#ifdef ON_PLATFORM_X86
	char* sys_uuid = NULL;

	sys_uuid = system_uuid();
	if(!sys_uuid)
		return NULL;

	if(hardware_id_match(sys_uuid, encrypted_hwid))
		return sys_uuid;
	else
		free(sys_uuid);
#endif

	return NULL;
}


/*
 * hardware_id_from_network_interface, get hardware information from network interface
 */
static char* hardware_id_from_network_interface(const char* encrypted_hwid)
{
	GList* list = NULL;
	char* mac = NULL;
	char* str = NULL;

	list = network_interface_list_create();
	if(!list)
		return NULL;

	str = network_interface_list_id_match(list, encrypted_hwid);

	// mac = strdup(((struct network_interface*)l->data)->mac);
	if (str != NULL)
		mac = strdup(str);

	network_interface_list_free(list);

	return mac;
}

/*
 * _is_block_device, check file with dev_name if a block device
 * return 1 if yes, or 0 if not
 */
static int _is_block_device(const char* dev_name)
{
	struct stat sb;

	if(!dev_name)
		return 0;

	if(stat(dev_name, &sb) == -1)
		return 0;

	return ((sb.st_mode & S_IFMT) == S_IFBLK);
}


/*
 * _root_device_from_proc_cmdline, get root device name from cmdline file
 */
static char* _root_device_from_proc_cmdline(const char* file_name)
{
	return command_line_get_root_device_name(file_name);
}

/*
 * _root_device_from_proc_mounts, get root device name from /proc/mounts
 */
static char* _root_device_from_proc_mounts()
{
	char* root_dev = NULL;
	struct mntent* ent;
	FILE *f;

	if(!file_stat(PROC_MOUNTS, S_IRUSR))
		return NULL;

	f = setmntent(PROC_MOUNTS, "r");
	if(NULL == f)
		return NULL;

	while(NULL != (ent = getmntent(f)))
	{
		if((strcmp(ent->mnt_dir, "/") == 0) && (strcmp(ent->mnt_fsname, "rootfs") != 0))
			break;
	}

	if(ent && ent->mnt_fsname)
		root_dev = strdup(ent->mnt_fsname);
	else
		root_dev = NULL;

//	if(root_dev)
//		printf("%s\n", root_dev);

	endmntent(f);

	return root_dev;
}

/*
 * root_device, get root device name
 */
static char* root_device()
{
	char* root_dev_name = NULL;

	root_dev_name = _root_device_from_proc_mounts();

	if(root_dev_name && _is_block_device(root_dev_name))
		return root_dev_name;

	if(root_dev_name)
	{
		free(root_dev_name);
		root_dev_name = NULL;
	}

	root_dev_name = _root_device_from_proc_cmdline(CMDLINE_FILE);

	if(root_dev_name && _is_block_device(root_dev_name))
		return root_dev_name;

	if(root_dev_name)
		free(root_dev_name);

	return NULL;
}

/*
 * harddisk_id_sata, get sata harddisk information from disk name
 */
static char* harddisk_id_sata(const char* disk_name)
{
	struct hd_driveid id;
	char serial_no[BUF_SIZE];
	char model[BUF_SIZE];
	char buf[2 * BUF_SIZE];
	int fd = -1;
	char *file;
	char *file_name = NULL;

	memset(serial_no, 0, BUF_SIZE);
	memset(model, 0, BUF_SIZE);
	memset(buf, 0, 2 * BUF_SIZE);

	file_name = basename(disk_name);
	asprintf (&file, "/dev/%s", file_name);
	fd = open(file, O_RDONLY | O_NONBLOCK);
	if(fd < 0) {
		free(file);
		return NULL;
	}
	free(file);
	if(!ioctl(fd, HDIO_GET_IDENTITY, &id)) {
		int i, j;

		j = 0;
		for(i = 0; i < SERIAL_NO_LEN; i++) {
			if(isgraph(id.serial_no[i])) {
				serial_no[j] = id.serial_no[i];
				j++;
			}
		}

		j = 0;
		for(i = 0; i < MODEL_LEN; i++) {
			if(isgraph(id.model[i])) {
				model[j] = id.model[i];
				j++;
			}
		}
		sprintf(buf, "%s_%s", model, serial_no);
	} else {
		close(fd);
		return NULL;
	}

	close(fd);
	return strdup(buf);
}

/*
 * nvme_get_information, get nvme device information
 */
static int nvme_get_information(const char* device, const char* info, char* buff, size_t buf_size)
{
	FILE* fp = NULL;
	char path[BUF_SIZE] = {0};
	char content[BUF_SIZE] = {0};
	int ret;
	int result = 0;
	int i = 0;
	int j = 0;
	char* p;
	int has_graph = 0;
	int content_length = 0;
	char* pa = NULL;

	if (!device || strlen(device) == 0 || !info || strlen(info) == 0)
	{
		return -1;
	}

	// sprintf(path, "/sys/block/%s/device/%s", device, info);
	asprintf(&pa, "/sys/block/%s/device/%s", device, info);
	// syslog(LOG_INFO,"path : [%s]",pa);

	fp = fopen(pa, "r");
	if (fp == NULL)
	{
		return -1;
	}

	ret = fread(content, 1, sizeof(content), fp);
	if (ret <= 0)
	{
		result = -1;
		goto out;
	}

	p = content + strlen(content);
	while (p != content)
	{
		if (!isgraph(*p))
		{
			*p = '\0';
		} else {
			break;
		}
		p--;
	}

	i = 0;
	j = 0;
	content_length = strlen(content);
	for (i = 0; i < content_length && j < buf_size; ++i)
	{
		if (isgraph(content[i]))
		{
			buff[j] = content[i];
			j++;
			has_graph = 1;
		} else {
			if (has_graph)
			{
				buff[j] = '-';
				j++;
				has_graph = 0;
			}
		}
	}

out:

	fclose(fp);

	return result;
}

/*
 * harddisk_id_nvme, get nvme harddisk information from disk name
 */
static char* harddisk_id_nvme(const char* partition_name)
{
	DIR *dp = NULL;
	struct dirent *dir = NULL;
	char buff[BUF_SIZE * 2] = {0};
	char serial_no[BUF_SIZE] = {0};
	char model[BUF_SIZE] = {0};
	char nvme_device[BUF_SIZE] = {0};

	dp = opendir("/sys/block");
	if (!dp)
	{
		return NULL;
	}

	while ((dir = readdir(dp)) != NULL)
	{
		if (strncmp(dir->d_name, partition_name, strlen(dir->d_name)) == 0)
		{
			strcpy(nvme_device, dir->d_name);
			break;
		}
	}

	closedir(dp);

	if (nvme_get_information(nvme_device, "model", model, sizeof(model)) < 0 ||
		nvme_get_information(nvme_device, "serial", serial_no, sizeof(serial_no)) < 0)
	{
		return NULL;
	}

	sprintf(buff, "%s_%s", model, serial_no);

	return strdup(buff);
}

/*
 * harddisk_id, get harddisk information from disk name
 */
static char* harddisk_id(const char* disk_name)
{
	char* result = NULL;
	char resolved_path[4096] = {0};
	char* file_name = NULL;

	result = realpath(disk_name, resolved_path);
	if (result == NULL)
	{
		return NULL;
	}

	if (!_is_block_device(resolved_path))
	{
		return NULL;
	}

	file_name = basename(resolved_path);
	if (strncmp(file_name, "nvme", strlen("nvme")) == 0)
	{
		return harddisk_id_nvme(file_name);
	}

	return harddisk_id_sata(disk_name);
}

/*
 * optical_drive_hwinfo, get optical drive device's hardware information
 */
static char* optical_drive_hwinfo()
{
	CdIo_t *p_cdio = NULL;
	cdio_hwinfo_t hwinfo;
	char buf[BUF_SIZE];

	p_cdio = cdio_open(NULL, DRIVER_DEVICE);
	if(NULL == p_cdio)
		return NULL;

	if(!cdio_get_hwinfo(p_cdio, &hwinfo))
		return NULL;

	memset(buf, 0, BUF_SIZE);

	sprintf(buf, "%s %s %s",
		hwinfo.psz_vendor,
		hwinfo.psz_model,
		hwinfo.psz_revision);

	return strdup(buf);
}

static char* bootdevice_cid()
{
	FILE * fp = NULL;
	char buf[BUF_SIZE] = {0};

	fp = fopen(CID_FILE, "r");
	if(!fp)
		return NULL;

	if(!fgets(buf, BUF_SIZE, fp)) {
		fclose(fp);
		return NULL;
	}

	for(int i = 0; i < BUF_SIZE; i++) {
		if((buf[i] == '\n') || (buf[i] == '\r'))
			buf[i] = '\0';
	}
	buf[BUF_SIZE - 1] = '\0';

	fclose(fp);
	
	return strdup(buf);
}

/*
 * get hardware information from proc bootdevide cid
 */
static char* hardware_id_from_bootdevice_cid(const char* encrypted_hwid)
{
	char* hw_cid = NULL;

	hw_cid = bootdevice_cid();
	if(!hw_cid)
		return NULL;

	if(hardware_id_match(hw_cid, encrypted_hwid)) {
		// printf("%s   %s\n",hw_cid,encrypted_hwid);
		return hw_cid;
	} else
		free(hw_cid);

	return NULL;
}

void removeSpacesTabs(char* str)
{
	int i, j = 0;
	for (i = 0; str[i]; i++) {
		if (str[i] != ' '&&str[i] != '\t')
			str[j++] = str[i];
	}
	str[j] = '\0';
}

bool is_huawei_9x0()
{
	GList* huawei_identifiers = NULL;
	bool _is_9x0 = false;
	char cpuinfo[BUF_SIZE] = {0};
	char cpuinfo_lower[BUF_SIZE] = {0};
	FILE *file;
	int i = 0;

	file = fopen(COUINFO_FILE, "r");
    if (file == NULL) {
        _is_9x0 = false;
		goto out;
    }

	huawei_identifiers = g_list_append(huawei_identifiers, "kirin990");
	huawei_identifiers = g_list_append(huawei_identifiers, "kirin9006c");
	huawei_identifiers = g_list_append(huawei_identifiers, "kirin9a0");

	while(fgets(cpuinfo, sizeof(cpuinfo), file) != NULL) {
		cpuinfo[BUF_SIZE-1] = '\0';
		// printf("origin: %s",cpuinfo);fflush(stdout);
		i = 0;
		while (cpuinfo[i] != '\0') {
			cpuinfo[i] = tolower(cpuinfo[i]);
			i++;
    	}
		// printf("lower: %s",cpuinfo);fflush(stdout);

		removeSpacesTabs(cpuinfo);
		// printf("removeSpacesTabs: %s",cpuinfo);fflush(stdout);

		if(strncasecmp(cpuinfo, "hardware", strlen("hardware")) == 0)
			for (GList *iter = huawei_identifiers; iter != NULL; iter = g_list_next(iter)) {
				if(g_strrstr(cpuinfo, iter->data) != NULL) {
					printf("%s, cpuinfo: %s\n", (const char*)iter->data,cpuinfo);fflush(stdout);
					_is_9x0 = true;
					goto out;
				}
			}
	}
	
out:
	if (file)
		fclose(file);

	if (huawei_identifiers)
		g_list_free(huawei_identifiers);

	return _is_9x0;
}


/*
 * hardware_id_encrypt, encrypt hardware information
 */
static char* hardware_id_encrypt(const char* hwid, const char* salt)
{
	char* encrypted_id = NULL;

	if(!hwid || !salt || (strlen(salt) == 0))
		return NULL;

	encrypted_id = encrypted_number_generate_kyhwid(NULL, hwid, salt);
	if(!encrypted_id)
		return NULL;

	return encrypted_id;
}

/*
 * hardware_id_encrypt, save hardware information
 */
static int hardware_id_save(const char* file, const char* hw_id)
{
	return activation_code_save(file, hw_id);
}

/*
 * hardware_id_load, load hardware information
 */
static char* hardware_id_load(const char* file)
{
	return activation_code_load(file);
}


/*
 * _hardware_id, get hardware information
 */
static char* _hardware_id(const char* file, int save_file)
{
	char* rootfs_dev = NULL;
	char* hd_id = NULL;
	char* max_mac = NULL;
#ifdef ON_PLATFORM_X86
	char* sys_uuid = NULL;
#endif
	//char* fs_uuid = NULL;
	char* encrypted_hwid = NULL;
	char* cd_hwid = NULL;
	char* hw_cid = NULL;

	if (is_huawei_9x0()) {
		hw_cid = bootdevice_cid();
		printf("hw_cid: %s",hw_cid);fflush(stdout);
		if(hw_cid) {
			encrypted_hwid = hardware_id_encrypt(hw_cid, "C");
			printf("encrypted_hwid: %s",encrypted_hwid);fflush(stdout);
			if(encrypted_hwid) {
				if(save_file) {
					if(!hardware_id_save(file, encrypted_hwid)) {
						free(encrypted_hwid);
						free(hw_cid);
						return NULL;
					}
				}
				return hw_cid;
			}
		}
	}

#ifdef ON_PLATFORM_X86
	sys_uuid = system_uuid();
	if(sys_uuid)
	{
		encrypted_hwid = hardware_id_encrypt(sys_uuid, "S");
		if(encrypted_hwid)
		{
			if(save_file)
			{
				if(!hardware_id_save(file, encrypted_hwid))
				{
					free(encrypted_hwid);
					free(sys_uuid);
					return NULL;
				}
			}

			return sys_uuid;
		}
	}
#endif

	rootfs_dev = getenv(ROOTFS_DEV);
	if(rootfs_dev)
	{
		hd_id = harddisk_id(rootfs_dev);
	}
	else
	{
		char* root_dev = root_device();
		if(root_dev)
		{
			hd_id = harddisk_id(root_dev);
			free(root_dev);
			root_dev = NULL;
		}
	}

	if(hd_id)
	{

		encrypted_hwid = hardware_id_encrypt(hd_id, "H");
		if(encrypted_hwid)
		{
			if(save_file)
			{
				if(!hardware_id_save(file, encrypted_hwid))
				{
					free(encrypted_hwid);
					free(hd_id);
					return NULL;
				}
			}

			free(encrypted_hwid);
			return hd_id;
		}
		if (hd_id)
			free(hd_id);
	}

	max_mac = network_interface_get_max_mac();
	if(max_mac)
	{

		encrypted_hwid = hardware_id_encrypt(max_mac, "N");
		if(encrypted_hwid)
		{
			if(save_file)
			{
				if(!hardware_id_save(file, encrypted_hwid))
				{
					free(encrypted_hwid);
					free(max_mac);
					return NULL;
				}
			}

			free(encrypted_hwid);
			return max_mac;
		}
		if (max_mac)
			free(max_mac);
	}
	
    /*
	fs_uuid = root_device_fs_uuid();
	if(fs_uuid)
	{
		encrypted_hwid = hardware_id_encrypt(fs_uuid, "F");
		if(encrypted_hwid)
		{
			if(save_file)
			{
				if(!hardware_id_save(file, encrypted_hwid))
				{
					free(encrypted_hwid);
					free(fs_uuid);
					return NULL;
				}
			}

			return fs_uuid;
		}
	}
    */
	
	if(!save_file)
	{
		cd_hwid = optical_drive_hwinfo();
		if(cd_hwid)
			return cd_hwid;
	}

	return NULL;
}

/*
 * hardware_id_match, check if hwid matches encrypted_hwid
 * return 1 if matches,  or 0 if not
 */
static int hardware_id_match(const char* hwid, const char* encrypted_hwid)
{
	char* encrypted_id = NULL;
	char* p = NULL;
	int ret = 0;

	if(!hwid || !encrypted_hwid || (strlen(encrypted_hwid) != HW_CODE_LENGTH))
		return 0;

	p = (char*)(encrypted_hwid + HW_CODE_LENGTH - 1);

	encrypted_id = hardware_id_encrypt(hwid, p);
	if(!encrypted_id)
		return 0;

	ret = (strcmp(encrypted_id, encrypted_hwid) == 0);

	free(encrypted_id);

	return ret;
}

/*
static GList* block_device_list_id_match(GList* list, const char* encrypted_hwid)
{
	GList* l;
	struct block_device* b;

	for(l = list; l != NULL; l = l->next)
	{
		if(!l->data)
			continue;

		b = (struct block_device*)l->data;
		if(!b->id)
			continue;

		if(hardware_id_match(b->id, encrypted_hwid))
		{
			return l;
		}
	}

	return NULL;
}
*/


/*
 * hardware_id_from_disk, get hardware information from harddisk
 */
static char* hardware_id_from_disk(const char* encrypted_hwid)
{
//	GList *list = NULL;
//	GList *l = NULL;
	char* rootfs_dev = NULL;
	char* hd_id = NULL;

	rootfs_dev = root_device();
	if(rootfs_dev)
	{
		hd_id = harddisk_id(rootfs_dev);
		free(rootfs_dev);
		rootfs_dev = NULL;
		if(hd_id)
		{
			if(hardware_id_match(hd_id, encrypted_hwid))
				return hd_id;
			else
			{
				free(hd_id);
				hd_id = NULL;
			}
		}
		
	}

	return NULL;

/*
	list = block_device_list_create();
	if(!list)
		return NULL;

	l = block_device_list_id_match(list, encrypted_hwid);
	if(!l)
	{
		block_device_list_free(list);
		return NULL;
	}

	hd_id = NULL;
	hd_id = strdup(((struct block_device*)(l->data))->id);

	block_device_list_free(list);

	return hd_id;
*/
}

/*
 * get_machine_info_from_sysfs, get service tag from system file.
 */ 
char* get_service_tag_from_sysfs(char* file_name)
{
	FILE * fp = NULL;
	char buf[BUF_SIZE];
	int i;

	fp = fopen(file_name, "r");
	if(!fp)
		return NULL;

	memset(buf, 0, BUF_SIZE);

	if(!fgets(buf, BUF_SIZE, fp))
	{
		fclose(fp);
		return NULL;
	}

	for(i = 0; i < BUF_SIZE; i++)
	{
		if((buf[i] == '\n') || (buf[i] == '\r'))
			buf[i] = '\0';
	}
	buf[BUF_SIZE - 1] = '\0';

	fclose(fp);

	return strdup(buf);
}

/*
 * get_machine_info_from_dmidecode, get service tag from dmidecode.
 */ 
char* get_service_tag_from_dmidecode(char* cmd)
{
	FILE* fp;
	char buf[BUF_SIZE];
	int i;

	if(access("/usr/sbin/dmidecode", F_OK | R_OK | X_OK) != 0)
		return NULL;

	fp = popen(cmd, "r");
	if(!fp)
		return NULL;

	memset(buf, 0, BUF_SIZE);
	if(!fgets(buf, BUF_SIZE, fp))
	{
		pclose(fp);
		return NULL;
	}

	for(i = 0; i < BUF_SIZE; i++)
	{
		if((buf[i] == '\n') || (buf[i] == '\r'))
			buf[i] = '\0';
	}
	buf[BUF_SIZE - 1] = '\0';

	pclose(fp);

	return strdup(buf);
}

/*
 * hardware_id_with_file, get kylin hardware information
 */
char* hardware_id_with_file(const char* file, int save_file)
{
	//判断是否为oem环境
	int ret=-1;
	char buf[256]={0};
	FILE *fp=fopen("/etc/LICENSE", "r");
	if(NULL!=fp)
	{
		while (NULL!=fgets(buf,256,fp))
		{
			if(strlen(buf)>6)
			{
				if(0==strncmp(buf,"METHOD",6))
				{
					if(NULL!=strstr(buf,"oem"))
					{
						ret=0;
						break;
					}
				}
			}
		}
		fclose(fp);
	}
	if(0==ret)
	{
		char *service_tag = NULL;
		service_tag = get_service_tag_from_sysfs("/sys/class/dmi/id/product_serial");
		if (service_tag)
			return service_tag;

		service_tag = get_service_tag_from_dmidecode("/usr/sbin/dmidecode -t 1 |grep -i 'Serial Number' |awk '{print $3}'");
		return service_tag;
	}
	uid = getpid();
	// syslog(LOG_INFO,"[%d] <%s/%d>: file path: %s.",uid,__func__,__LINE__,file);
	char* encrypted_hwid = NULL;
	char* result = NULL;
	char* hardware_id = NULL;

	encrypted_hwid = hardware_id_load(file);

	if(!encrypted_hwid) {
		hardware_id = _hardware_id(file, save_file);
		// syslog(LOG_INFO,"[%d] <%s/%d>: hardware_id: %s.",uid,__func__,__LINE__,hardware_id);
		return hardware_id;
	}

	if(strlen(encrypted_hwid) != HW_CODE_LENGTH)
		return NULL;

	if(encrypted_hwid[HW_CODE_LENGTH - 1] == 'S')
	{
		result = hardware_id_from_system_uuid(encrypted_hwid);

		g_free(encrypted_hwid);
		return result;
	}
	else if(encrypted_hwid[HW_CODE_LENGTH - 1] == 'H')
	{
		result = hardware_id_from_disk(encrypted_hwid);

		g_free(encrypted_hwid);
		return result;
	}
	else if(encrypted_hwid[HW_CODE_LENGTH - 1] == 'N')
	{
		// syslog(LOG_INFO,"[%d] <%s/%d>: encrypted_hwid: %s.",uid,__func__,__LINE__,encrypted_hwid);
		result =  hardware_id_from_network_interface(encrypted_hwid);
		// syslog(LOG_INFO,"[%d] <%s/%d>: result: %s.",uid,__func__,__LINE__,result);
		g_free(encrypted_hwid);
		return result;
	}
	else if(encrypted_hwid[HW_CODE_LENGTH - 1] == 'F')
	{
		result = hardware_id_from_fs_uuid(encrypted_hwid);

		g_free(encrypted_hwid);
		return result;
	}
	else if(encrypted_hwid[HW_CODE_LENGTH - 1] == 'C')
	{
		result = hardware_id_from_bootdevice_cid(encrypted_hwid);

		g_free(encrypted_hwid);
		return result;
	}
	else
	{
		g_free(encrypted_hwid);
		return NULL;
	}

	return NULL;
}

/*
 * hardware_id_save_kyhwid, get kylin hardware information and save it
 */
char* hardware_id_save_kyhwid()
{
	return hardware_id_with_file(KYLIN_HWID, 1);
}

/*
 * hardware_id_save_no_kyhwid, get kylin hardware information but do not save it
 */
char* hardware_id_save_no_kyhwid()
{
	return hardware_id_with_file(KYLIN_HWID, 0);
}

#if 0
int main(int argc, char** argv)
{

	char* hw_id;

	hw_id = hardware_id();
	if(hw_id)
	{
		printf("%s\n", hw_id);
		free(hw_id);
	}

	return 0;
}
#endif
